package vip.liux.front.infrastructure.aop.interceptor;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.util.StopWatch;
import vip.liux.front.domain.enums.EntityChangeType;
import vip.liux.front.infrastructure.utils.AmbientScopeProvider;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AuditingManager implements IAuditingManager {

    private static final String AMBIENT_CONTEXT_KEY = "AuditLogScope";

    private final IAuditingStore auditingStore;
    private final IAuditingHelper auditingHelper;
    private final AmbientScopeProvider<AuditLogScope> ambientScopeProvider;

    public AuditingManager(IAuditingStore auditingStore, IAuditingHelper auditingHelper, AmbientScopeProvider<AuditLogScope> ambientScopeProvider) {
        this.auditingStore = auditingStore;
        this.auditingHelper = auditingHelper;
        this.ambientScopeProvider = ambientScopeProvider;
    }

    @Override
    public AuditLogScope getCurrent() {
        return ambientScopeProvider.getValue(AMBIENT_CONTEXT_KEY);
    }

    @Override
    public IAuditLogSaveHandle beginScope() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        AutoCloseable scope = ambientScopeProvider.beginScope(
                AMBIENT_CONTEXT_KEY,
                new AuditLogScope(auditingHelper.createAuditLogInfo())
        );

        return new CloseableSaveHandle(this, scope, getCurrent().log(), stopWatch);
    }

    protected void executePostContributors(AuditLogInfo auditLogInfo) {

    }

    protected void beforeSave(CloseableSaveHandle saveHandle) {
        saveHandle.stopWatch.stop();
        saveHandle.auditLog.setExecutionDuration((int) saveHandle.stopWatch.lastTaskInfo().getTimeMillis());

        executePostContributors(saveHandle.auditLog);
        mergeEntityChanges(saveHandle.auditLog);
    }

    protected void mergeEntityChanges(AuditLogInfo auditLog) {
        Map<ImmutablePair<String, String>, List<EntityChangeInfo>> changeGroups = auditLog.getEntityChanges()
                .stream().filter(e -> e.getChangeType() == EntityChangeType.Updated)
                .collect(Collectors.groupingBy(e -> new ImmutablePair<>(e.getEntityTypeFullName(), e.getEntityId())));

        for (Map.Entry<ImmutablePair<String, String>, List<EntityChangeInfo>> changeGroup : changeGroups.entrySet()) {
            List<EntityChangeInfo> entityChangeInfos = changeGroup.getValue();
            if (entityChangeInfos.size() <= 1) {
                continue;
            }

            var firstEntityChange = entityChangeInfos.get(0);

            for (var entityChangeInfo : entityChangeInfos) {
                if (entityChangeInfo == firstEntityChange) {
                    continue;
                }

                firstEntityChange.merge(entityChangeInfo);

                auditLog.getEntityChanges().remove(entityChangeInfo);
            }
        }

    }

    protected void save(CloseableSaveHandle saveHandle) {
        beforeSave(saveHandle);

        this.auditingStore.save(saveHandle.auditLog);
    }

    protected static class CloseableSaveHandle implements IAuditLogSaveHandle {
        private final AutoCloseable closeable;

        private final AuditingManager auditingManager;

        private final AuditLogInfo auditLog;

        private final StopWatch stopWatch;


        protected CloseableSaveHandle(AuditingManager auditingManager, AutoCloseable closeable, AuditLogInfo auditLog, StopWatch stopWatch) {
            this.auditLog = auditLog;
            this.closeable = closeable;
            this.auditingManager = auditingManager;
            this.stopWatch = stopWatch;
        }

        @Override
        public void close() throws Exception {
            closeable.close();
        }

        @Override
        public void save() {
            auditingManager.save(this);
        }
    }
}
